package com.wnxy.sddfp.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.sddfp.common.constant.UserConstant;
import com.wnxy.sddfp.common.result.Result;
import com.wnxy.sddfp.common.result.enums.IErrorCode;
import com.wnxy.sddfp.common.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.common.util.JwtTemplate;
import com.wnxy.sddfp.gateway.config.IgnorePathConfig;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.sql.Connection;
import java.time.Duration;
import java.util.List;

/**
 * 网关全局过滤器，对所有的请求拦截并进行访问权限的校验。
 * 网关过滤器：
 * 1、GatewayFilter网关过滤器：是局部过滤器，只对配置了该过滤器的路由生效。
 * 2、GlobalFilter全局过滤器：对所有路由都生效。需要实现该接口。
 */
@Configuration  // 配置类
public class AuthFilter implements GlobalFilter {

    // 注入放行的路径集合
    @Autowired
    private IgnorePathConfig ignorePathConfig;
    @Autowired
    private JwtTemplate jwtTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 常量定义
    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer ";

    /**
     * 请求头：
     *      Authorization Bearer xxx
     */
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //1、获取请求路径， 放行资源： swagger、登录资源
        //1.1、获取请求路径
        String path = request.getURI().getPath();
        //1.2、放行资源：登录注册地址需要放行、swagger相关地址也要放行.
        List<String> uris = ignorePathConfig.getUris();
        PathMatcher pathMatcher = new AntPathMatcher();
        if (uris.stream().anyMatch(uri -> pathMatcher.match(uri, path))) {
            // 放行
            return chain.filter(exchange);
        }

        //2、除了放行的资源，其他资源必须要认证后才能访问；
        //所以这里需要先获取请求头，去掉token前缀“Bearer ”， 得到token
        String authToken = request.getHeaders().getFirst(AUTHORIZATION);
        if (StringUtils.isEmpty(authToken)) {
            // 传入response对象、错误状态码枚举对象
            return errorResponse(response,BusinessCode.TOKEN_ISNULL);
        }
        // 去掉token前缀“Bearer ”
        authToken = authToken.replace(BEARER,"");


        //3、校验token
        //3.1、校验token的合法性
        if (!jwtTemplate.verify(authToken)){
            return errorResponse(response,BusinessCode.TOKEN_INVALID);
        }

        //3.2、解析获取token数据： userId、phone
        String userId = (String) jwtTemplate.parseToken(authToken, UserConstant.TOKEN_USERID);
        String phone = (String) jwtTemplate.parseToken(authToken, UserConstant.TOKEN_PHONE);

        //3.3、校验token是否过期
        String key = UserConstant.USER_TOKEN + userId;
        if (!stringRedisTemplate.hasKey(key)) {
            // 如果redis中没有token，说明token过期
            return errorResponse(response,BusinessCode.TOKEN_EXPIRATION);
        }
        //3.3、token 续期 （Redis保存登录状态，也就是把登录后的token存储到redis中）
        stringRedisTemplate.expire(key, Duration.ofMinutes(30));

        //4、网关中重写请求头，携带userId;  这样从网关中转发到微服务就会携带请求头userId，在微服务中就可以直接获取登录用户的userId
        request.mutate().header(UserConstant.TOKEN_USERID,userId);
        request.mutate().header(UserConstant.TOKEN_PHONE,phone);

        return chain.filter(exchange);
    }

    private static Mono<Void> errorResponse(ServerHttpResponse response, IErrorCode errorCode) throws Exception {
        Result result = Result.fail(errorCode);
        byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
        DataBuffer dataBuffer= response.bufferFactory().wrap(bytes);
        // 返回错误
        return response.writeWith(Mono.just(dataBuffer));
    }

    public static void main(String[] args) {
        PathMatcher pathMatcher = new AntPathMatcher();
        // /* 匹配一级目录，必须有，不能省略
        // /** 匹配任意多级目录，也可以省略
        boolean match = pathMatcher.match("/**/user/test", "/user/test");
        System.out.println("match = " + match);
    }
}
